/*Name: elgamal.cpp
  Last Modification: 31/05/10 23:20
  Description: Header de la classe rsa. Cet algorithme utilise les propriétées des nombres
  premiers pour assurer la sécurité du cryptosystéme. C'est un chiffrement asymétrique très
  utilisé de nos jours pour communiquer des données sensibles comme pour les banques.
  C'est également ce système que l'on retrouve dans nos ordinateurs lorsque l'on souhaite
  se connecter de façcon securisé à un autre ordinateur (voir ssh)
  License: GNU GPL v3
    Copyright (C) 2012 G. Bonnoron, P.Brunet, C. Duchene et F. Cornevaux-Juignet

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.*/

//inclusion des bibliothèques
#include "elgamal.h"

//constructeur de l'algorithme
Elgamal::Elgamal() : Algorithme()
{
    p=0;        //nombre premier
    q=0;        //q = p -1
    h=0;
    g=0;        //g générateur de Z/pZ*
    x=0;        //x aléa dans Z/qZ
    corps = new QVBoxLayout();
    bouton = new QPushButton(QString::fromUtf8("Gestion des clés"));
    texte0 = new QLabel("Obligatoire");
    cleq = new QLineEdit(QString::fromUtf8("Entrez la clé q "));
    texte1 = new QLabel("Chiffrement");
    cleg = new QLineEdit(QString::fromUtf8("Entrez la clé g"));
    cleh = new QLineEdit(QString::fromUtf8("Entrez la clé h"));
    texte2 = new QLabel(QString::fromUtf8("Déchiffrement"));
    clex = new QLineEdit(QString::fromUtf8("Entrez votre clé privée"));
    crypter = new QTextEdit(QString::fromUtf8("Vos données (dé)chiffrées apparaîtront ici"));
    fenetre = new QWidget();
    fenetre->setWindowIcon(QIcon("q4.ico"));
    barre = new QProgressBar();
    label_h = new QLabel();
    label_g = new QLabel();
    label_q = new QLabel();
    label_x = new QLabel();
    barre->setVisible(false);
    clefichier = new QLineEdit();
    clefichier2 = new QLineEdit(QString::fromUtf8("ou entrez ici un fichier contenant les clés"));
    coche = new QCheckBox(QString::fromUtf8("Sauvegarder la clé"));
    newKey();
    generercle();
    QObject::connect(bouton, SIGNAL(clicked()), this->fenetre, SLOT(show()));
}

//fonction d'initialisation des variables
void Elgamal::initialisation()
{
    //l'utilisateur a entré des clés fichier
    if(clefichier2->isModified() && clefichier2->text() != "")
    {
        QFile file(clefichier2->text());
        file.open(QIODevice::ReadOnly | QIODevice::Truncate);
        cleq->setText(file.readLine());
        cleh->setText(file.readLine());
        cleg->setText(file.readLine());
        clex->setText(file.readLine());
        q = mpz_class(cleq->text().toStdString());
        h = mpz_class(cleh->text().toStdString());
        g = mpz_class(cleg->text().toStdString());
        x = mpz_class(clex->text().toStdString());
        file.close();
    }
    else{
        if(cleq->isModified())              //cela signifie que l'utilisateur a lui-même entré ses clés puisque q est obligatoire.
        {
            q=mpz_class((cleq->text()).toStdString());
            p = q + 1;
            if(clex->isModified())                           //x n'est nécessaire que pour le déchiffrement
                x=mpz_class((clex->text()).toStdString());
            if(cleg->isModified())
                g=mpz_class((cleg->text()).toStdString());      //g et h sont nécessaires pour le chiffrement
            if(cleh->isModified())
                h=mpz_class((cleh->text()).toStdString());
        }
        else                                                //on prend les clés pré-générées.
        {
            cleq->setText(QString::fromStdString(q.get_str()));
            clex->setText(QString::fromStdString(x.get_str()));
            cleg->setText(QString::fromStdString(g.get_str()));
            cleh->setText(QString::fromStdString(h.get_str()));
        }
      }
}

//fonction permettant le chiffrement de notre message
void Elgamal::chiffre(bool fich)
{
    //On initialise tout notre bazar
    QTime mesure;
    mpz_class y = 0;
    mpz_class c = 0;
    mpz_class s = 0;
    mpz_class m = 0;
    gmp_randstate_t etat;
    mpz_class motascii = 0;
    gmp_randinit_default(etat);
    initialisation();
    mesure.start();
    //on traite différement le cas où on est dans un fichier
    if(fich){
        barre->setVisible(true); //un fichier c'est gros donc on met une barre d'avancement
        long taille = line.size();  //on prend une référence pour connaitre l'avancement total
        lineOut.clear();    //on nettoie tout
        lineOut.append("##fichier de chiffrement ElGamal pour plate-forme Qrypt4all\n");        //pour éviter bien des soucis après !
        for (int i = 0; i<line.length();i++){   //Pour chaque lettre
            motascii = (unsigned char)line[i];  //on prend sa valeur ascii étendu
            m = m*257+motascii+1;   //on décale les lettres déjà enregistrées puis on ajoute la nôtre décalé de 1 pour être entre 1 et 256
            if(m*257+256>p || i == line.length()-1){    //Si on risque de sortir de notre corps d'action ou qu'on arrive à la fin du texte
                gmp_randseed_ui(etat, time(NULL));  //on génère une nouvelle graine
                mpz_urandomm(y.get_mpz_t(), etat, q.get_mpz_t()); //y est un nouveau nombre aléatoire...
                mpz_powm(c.get_mpz_t(),g.get_mpz_t(),y.get_mpz_t(),p.get_mpz_t()); //c = g^y [p]
                mpz_powm(s.get_mpz_t(),h.get_mpz_t(),y.get_mpz_t(),p.get_mpz_t()); //s = h^y [p]
                m = (m * s)%p;  //on fini de coder
                lineOut += QString::fromStdString(c.get_str() + ";" + m.get_str() + ";");  //et on écrit dans le fichier
                m=0;   //on remet à zéro les caractéres stockés pour être codés
                barre->setValue(100 * i / taille + 1); //et on met à jour la barre d'avancement
            }
        }
    }
    else{   //si ce n'est pas un fichier binaire
        barre->setVisible(false);   //pas besoin de la barre d'avancement
        messageOut = "";    //on vide tout
        for(int i = 0; i<messageIn.length();i++){   //pour chaque caractéres
            if(messageIn[i].toAscii() > q + 32)         //la clé ne peut pas chiffrer ce message (en pratique, ça n'arrive jamais !!!)
            {
                messageOut = QString::fromUtf8("Votre message ne peut être chiffré avec cette clé !");
                break;
            }
            m = m*(127-32)+messageIn[i].toAscii()+1-32; //on décale les lettres stockées et on ajoute la dernière
            if(m*(127-32)+(127-32)>p || i == messageIn.length()-1){ //si on risque de sortir du lieu d'action ou qu'on est à la fin du texte
                gmp_randseed_ui(etat, time(NULL));  //on prend une graine
                mpz_urandomm(y.get_mpz_t(), etat, q.get_mpz_t()); //y est un nouveau nombre aléatoire...
                mpz_powm(c.get_mpz_t(),g.get_mpz_t(),y.get_mpz_t(),p.get_mpz_t()) ; //c = g^y [p]
                mpz_powm(s.get_mpz_t(),h.get_mpz_t(),y.get_mpz_t(),p.get_mpz_t()) ; //s = h^y [p]
                m = (m * s)%p;  //et on fini de coder
                messageOut += QString::fromStdString(c.get_str() + ";" + m.get_str() + ";");    //puis on affiche le résultat
                m=0;    //et on vide le tampon pour recommencer
            }
        }
    }
    this->outElgamal(fich); //on affiche le resultat
    temps = mesure.elapsed();
}

//et maintenant pour dechiffrer
void Elgamal::dechiffre(bool fich)
{
    mpz_class res;
    unsigned long temp;
    mpz_class tempo;
    mpz_class c = 0;
    mpz_class m = 0;
    mpz_class s = 0;
    mpz_class invs = 0;
    QTime mesure;
    initialisation();
    mesure.start();
    if(fich){   //si on est dans un fichier
        QByteArray buff;
        barre->setVisible(true);    //c'est gros on met la barre d'avancement
        long taille = lineOut.size();   //on prend la taille totale pour connaitre l'avancement
        lineOut.clear();    //on vide tout
        if(line.contains("##fichier de chiffrement ElGamal pour plate-forme Qrypt4all\n"))      //pour éviter que la plate-forme plante. En effet, elle s'attend à trouver nombre;nombre;... et si ce n'est pas le cas, elle plante...
        {
            int i = 60;                 //comptez, c'est la taille du message ci-dessus...
            while(i<line.length())
            {
                QString temp1;
                while(line[i] != ';')   //tant qu'on est pas au ";"
                {
                    temp1 += line[i];       //au colle les chiffres les uns à la suite des autres
                    i++;
                    barre->setValue(100 * i /taille);   //on met à jour l'avancement
                }
                i++;    //pour compter le ";"
                QString temp2;
                while(line[i] != ';')   //pareil pour le deuxième chiffre
                {
                    temp2 += line[i];      //c'est le deuxième nombre, d
                    i++;
                    barre->setValue(100 * i /taille);
                }
                i++;

                c = mpz_class(temp1.toStdString()); //on transforme le string en mpz_class
                m = mpz_class(temp2.toStdString());
                mpz_powm(s.get_mpz_t(),c.get_mpz_t(),x.get_mpz_t(),p.get_mpz_t());  //on fait nos clefs
                invs = 0;
                mpz_invert(invs.get_mpz_t(), s.get_mpz_t(), p.get_mpz_t()); //on calcule l'inverse de s modulo p ( c'est long...)
                buff.clear();   //on nettoye notre buffer
                res = (m * invs)%p; //et on décode m
                while(res>0){   //puis on affiche caractère par caractère en base 256 sans oublier le décalage de 1
                    tempo= (res-1)%257;
                    temp = tempo.get_ui();
                    buff.prepend(temp);
                    res = res/257;
                }
                lineOut.append(buff);   //on écrit notre buffer à la fin du lineOut
                barre->setValue(100 * i /taille + 1);   //on met à jour l'avancement
            }
            barre->setValue(100);   //(au moins on est sûr de finir à 100% ...)
        }
        else
        {
            lineOut.append(QString::fromUtf8("Le fichier d'entrée ne correspondait pas à un fichier prévu pour Qrypt4all."));
            //eh oui !
        }
    }
    else{   //si on traite juste une entrée plate-forme
        //en gros, on fait pareil que pour les fichiers à part le passage dans les positifs qui se fait tout seul avec le cast char.
        QString buff;
        int i = 0;
        barre->setVisible(false);   //on ne met pas la barre d'avancement c'est trop rapide
        messageOut = "";    //on vide tout
        while(i<messageIn.length()) //pour chaque caractre
        {
            QString temp1;
            while(messageIn[i] != ';')  //on les colle jusqu'au premier ";" pour faire un nombre
            {
                temp1 += messageIn[i];
                i++;
            }
            i++;
            QString temp2;  //pareil jusqu'au deuxième ";"
            while(messageIn[i] != ';')
            {
                temp2 += messageIn[i];
                i++;
            }
            i++;
            c = mpz_class(temp1.toStdString());    //on transforme tout ça en mpz_class
            m = mpz_class(temp2.toStdString());
            mpz_powm(s.get_mpz_t(),c.get_mpz_t(),x.get_mpz_t(),p.get_mpz_t()); //on crée nos clefs
            invs = 0;
            mpz_invert(invs.get_mpz_t(), s.get_mpz_t(), p.get_mpz_t());
            res = (m * invs)%p;    //et on décode
            buff="";   //on vide le buffer
            while(res>0){  //et on sort lettre par lettre sans oublier le décalage
                tempo= (res-1)%(127-32);
                temp = tempo.get_ui();
                QChar sortie = QChar((char) temp + 32);
                buff = sortie + buff;  //on remplit le buffer
                res = res/(127-32);    //on met à jour ce qu'il reste à afficher
            }
            messageOut += buff;    //puis on met le buffer à la fin du message de sortie
        }
    }

    if(!line.contains("##fichier de chiffrement ElGamal pour plate-forme Qrypt4all\n")&&fich)
        crypter->setText(QString::fromUtf8("Le fichier d'entrée ne correspond pas à un fichier prévu pour Qrypt4all."));
    else
        this->outElgamal(fich); //on affiche tout ca

    temps = mesure.elapsed();
}

//fonction pour que l'on puisse voir quelque chose en sortie
void Elgamal::outElgamal(bool fichier)
{
    if(fichier){    //si on traite un fichier
        if(fileOut.isEmpty())   //on regarde si l'utilisateur n'a pas entrée de nom
            fileOut = "out_ElGamal.txt";    //on en met un par default
        QFile file(fileOut);    //puis on met tout dans ce fichier
        if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
            crypter->setText(QString("Erreur lors du l'écriture fichier"));
        else{
            file.write(lineOut);
            crypter->setText(QString("sortie dans " + fileOut));
        }

    }
    else{
        crypter->setText(messageOut);   //sinon on affiche dans la console
    }
}

//fonction qui envoie les widgets nécessaires à l'affichage
QList<QWidget*> Elgamal::affiche()
{
    QList<QWidget* > liste = QList<QWidget *>();
    QLabel* texte = new QLabel(QString::fromUtf8("<h1>Système d'ElGamal :</h1>"));
    liste.append(texte);
    liste.append(bouton);
    liste.append(texte0);
    liste.append(cleq);
    liste.append(texte1);
    liste.append(cleh);
    liste.append(cleg);
    liste.append(texte2);
    liste.append(clex);
    liste.append(clefichier2);
    liste.append(barre);
    liste.append(crypter);
    return liste;
}

//fonction qui permet de générer des clefs automatiquement et de les donner à l'utilisateur
void Elgamal::generercle()
{
    QVBoxLayout * layout = new QVBoxLayout();
    layout->addWidget(new QLabel(QString::fromUtf8("Ceci est la clé publique : ")));
    layout->addWidget(label_h);
    layout->addWidget(label_g);
    layout->addWidget(label_q);
    layout->addWidget(new QLabel(QString::fromUtf8("")));
    layout->addWidget(new QLabel(QString::fromUtf8("Ceci est votre clé privée : ")));
    layout->addWidget(label_x);
    QHBoxLayout * layout2 = new QHBoxLayout();
    layout->addWidget(coche);
    layout2->addWidget(new QLabel("Entrez un nom de fichier dans lequel vous enregistrerez : "));
    layout2->addWidget(clefichier);
    layout->addLayout(layout2);
    QHBoxLayout * layout3 = new QHBoxLayout();
    QWidget * button = new QPushButton(QString::fromUtf8("C'est noté !"));
    layout3->addWidget(new QLabel(""));
    layout3->addWidget(button);
    QWidget * button2 = new QPushButton(QString::fromUtf8("Nouvelles clefs"));
    layout3->addWidget(new QLabel(""));
    layout3->addWidget(button2);
    layout3->addWidget(new QLabel(""));
    layout->addLayout(layout3);
    fenetre->setLayout(layout);
    fenetre->setWindowTitle(QString::fromUtf8("Clés ElGamal"));
    fenetre->setWindowIcon(QIcon("q4.ico"));
    QObject::connect(button, SIGNAL(clicked()), this->fenetre, SLOT(close()));
    QObject::connect(button2, SIGNAL(clicked()), this, SLOT(newKey()));
    QObject::connect(button2, SIGNAL(clicked()), this, SLOT(initialisation()));
    QObject::connect(button, SIGNAL(pressed()), this, SLOT(save()));
}

void Elgamal::newKey(){
    gmp_randstate_t etat;
    gmp_randinit_default(etat);
    gmp_randseed_ui(etat, time(NULL));
    mpz_urandomb(p.get_mpz_t(), etat, 100);
    //p est maintenant créé !
    mpz_nextprime(p.get_mpz_t(), p.get_mpz_t());
    //p doit être premier, il est maintenant "opérationnel"
    q = p - 1;
    //q est maintenant créé !
    while(g == 0 || g >= p )
        mpz_urandomb(g.get_mpz_t(), etat, 100);
    //g est maintenant créé !
    while(x == 0 || x >= q )
        mpz_urandomb(x.get_mpz_t(), etat, 100);
    //x est maintenant créé
    mpz_powm(h.get_mpz_t(),g.get_mpz_t(),x.get_mpz_t(),p.get_mpz_t());
    //h est maintenant créé !
    label_h->setText(QString::fromUtf8("clé h = ") + QString::fromStdString(h.get_str()));
    label_g->setText(QString::fromUtf8("clé g = ") + QString::fromStdString(g.get_str()));
    label_q->setText(QString::fromUtf8("clé q = ") + QString::fromStdString(q.get_str()));
    label_x->setText(QString::fromUtf8("clé privée = ") + QString::fromStdString(x.get_str()));
}

//destructeur de la classe
Elgamal::~Elgamal()
{
}

//fonction necesaire à l'export
extern "C" MY_EXPORT Algorithme* maker()
{
    return new Elgamal();
}

//fonction permettant d'avoir une aide perso
QString Elgamal::aide()
{
    QString sortie;
    sortie.append(QString::fromUtf8("<h1>Cryptosystème d'ElGamal</h1>"));
    sortie.append(QString::fromUtf8("<h2>Le principe</h2><div align = 'justify'> Cet algorithme a été mis en place par Taher ELGAMAL en 1985 et se base sur l'arithmétique des groupes cycliques. Des connaissances en mathématiques sont requises pour en comprendre son fonctionnement intégral."));
    sortie.append(QString::fromUtf8("<p>1- Soit p nombre premier</p><p>2- on prend g un des générateurs de Z/pZ* et on note q = p - 1</p><p>3- on choisit x dans Z/qZ de façon aléatoire</p><p>4- soit h = g^x[p]</p>"));
    sortie.append(QString::fromUtf8("La clé publique est alors le triplet (h; g; q), alors que x est retenu comme étant la clé privée."));
    sortie.append(QString::fromUtf8("<p>Pour le chiffrement, ces étapes sont à respecter : <p>1- soit y in Z/qZ choisit aléatoirement </p><p>2- on pose c = g^y [p] et s = h^y[p]</p>Ensuite, on chiffre un message m de Z/pZ. Le message chiffré consiste en le couple (c ; d = s * m)</p>"));
    sortie.append(QString::fromUtf8("<p>A charge maintenant au destinataire de déchiffrer le message. On rappelle que cette personne détient la clé privée x : <p>1- s est trouvé en faisant c^x = (g^y)^x = g^(xy) = (g^x)^y = h^y = s [p] CQFD.</p><p>2- m est retrouvé en faisant d * s^(-1) = s* m * s^(-1) = m [p] CQFD </p></p>"));
    sortie.append(QString::fromUtf8("On remarquera que l'apparition d'aléatoire dans le chiffrement ne change rien ! Ainsi, un même bloc peut être chiffrée de plusieurs façons ! Là réside toute la force de cet algorithme.</div>"));
    sortie.append(QString::fromUtf8("<h2>L'utiliser dans le cadre de la plate-forme</h2><div align = 'justify'> Lorsque vous choisissez cet algorithme, une clé est automatiquement générée. Vous pouvez la consulter en cliquant sur gestion des clés."));
    sortie.append(QString::fromUtf8("Si vous souhaitez utiliser la clé générée par le module, il est inutile de les rentrer. Ne touchez pas aux champs, ils seront remplis automatiquement au moment du chiffrement / déchiffrement. "));
    sortie.append(QString::fromUtf8("En revanche, si vous souhaitez utiliser votre propre clé, il vous faudra remplir le champ de la clé q. Pour le chiffrement, les clés h et g cont nécessaires, pour le déchiffrement, c'est la clé privée."));
    sortie.append(QString::fromUtf8("<strong> Attention !</strong> Compte-tenu de l'algorithme, tout n'est pas possible pour cet algorithme. Prenez également garde que dans le cadre des fichiers pour le déchiffrement, il s'agisse bien d'un fichier chiffré en ElGamal ! Laissez vous surprendre par ce chiffrement original... </div>"));
    return sortie;
}

//fonction retournant le nom de l'algo
QString Elgamal::nom()
{
    return "ElGamal";
}

//fonction permettant de sauver les clés générées

void Elgamal::save()
{
    if(coche->isChecked())
    {
        QString fichier = clefichier->text();
        if(fichier == "" )
            fichier = "cles_ElGamal";
        QFile file(fichier);
        file.open(QIODevice::WriteOnly | QIODevice::Truncate);
        QByteArray line;
        line.append(QString::fromStdString(q.get_str() + '\n'));
        line.append(QString::fromStdString(h.get_str() + '\n'));
        line.append(QString::fromStdString(g.get_str() + '\n' + x.get_str()));
        file.write(line);
        file.close();
    }
}
